package emails

import (
	"crypto/tls"
	"errors"
	"fmt"
	"github.com/emersion/go-imap"
	"github.com/emersion/go-imap/client"
	"github.com/emersion/go-message"
	"github.com/emersion/go-message/charset"
	"github.com/emersion/go-message/mail"
	"github.com/emersion/go-sasl"
	"github.com/emersion/go-smtp"
	"io"
	"io/ioutil"
	"log"
	"strings"
	"time"
)

type ImapSetting struct {
	Url    string
	UseSSL bool
}

type SmtpSetting struct {
	Url    string
	UseSSL bool
}

type UserInfo struct {
	UserName string
	Password string
}

type Email struct {
	SeqNum    uint32
	MessageId string
	Subject   string
	TextBody  string
	HtmlBody  string
	DateTime  time.Time
	From      []string
	To        []string
}

func init() {
	message.CharsetReader = func(encoding string, input io.Reader) (reader io.Reader, e error) {
		return charset.Reader(encoding, input)
	}
}

func SendMessage(setting *SmtpSetting, info *UserInfo, to []string, showName string, subject string, body string) (err error) {
	//auth := sasl.NewPlainClient("", info.UserName, info.Password)
	//
	//// Connect to the server, authenticate, set the sender and recipient,
	//// and send the email all in one step.
	//msg := strings.NewReader("To: " + to[0] + "\r\n" +
	//	"Subject: " + subject + "\r\n" +
	//	"\r\n" +
	//	body + "\r\n")
	//err = smtp.SendMail(setting.Url, auth, showName, to, msg)
	//return
	// Connect to the remote SMTP server.
	//c, err := smtp.Dial("mail.example.com:25")

	//smtp.SendMail()

	c, err := smtp.DialTLS(setting.Url, &tls.Config{
		InsecureSkipVerify: setting.UseSSL,
	})
	if err != nil {
		return
	}

	c.Auth(sasl.NewPlainClient("", info.UserName, info.Password))

	// Set the sender and recipient first
	if err = c.Mail(info.UserName, nil); err != nil {
		return
	}

	for _, item := range to {
		if err = c.Rcpt(item); err != nil {
			return
		}
	}

	// Send the email body.
	wc, err := c.Data()
	if err != nil {
		return
	}

	headers := make(map[string]string)
	headers["From"] = (&mail.Address{Name: showName, Address: info.UserName,}).String()
	headers["To"] = to[0]
	headers["Subject"] = subject
	headers["Content-type"] = "text/plain; charset=utf-8"

	message := ""
	for k, v := range headers {
		message += fmt.Sprintf("%s: %s\r\n", k, v)
	}

	message += "\r\n" +
		body + "\r\n"

	_, err = fmt.Fprintf(wc, message)
	if err != nil {
		return
	}
	err = wc.Close()
	if err != nil {
		return
	}

	// Send the QUIT command and close the connection.
	err = c.Quit()
	if err != nil {
		return
	}

	return
}

func ReceiveMessage(imapSetting *ImapSetting, userInfo *UserInfo, fc func(*Email)) (err error) {
	defer func() {
		if r_err, ok := recover().(error); ok && r_err != nil {
			log.Println("接受消息是发生错误:" + r_err.Error())
		}
	}()

	var emailClient *client.Client

	if imapSetting.UseSSL {
		emailClient, err = client.DialTLS(imapSetting.Url, nil)
	} else {
		emailClient, err = client.Dial(imapSetting.Url)
	}
	if err != nil {
		log.Fatal(err)
	}

	defer emailClient.Close()

	emailClient.Login(userInfo.UserName, userInfo.Password)
	defer emailClient.Logout()

	emailClient.Select("INBOX", true)

	criteria := imap.NewSearchCriteria()
	criteria.WithoutFlags = []string{imap.AnsweredFlag, imap.SeenFlag}
	criteria.Since = time.Now().Add(time.Duration(-24) * time.Hour)
	uids, err := emailClient.Search(criteria)

	if err != nil {
		return
	}

	seqset := new(imap.SeqSet)
	seqset.AddNum(uids...)
	var section imap.BodySectionName

	items := []imap.FetchItem{
		imap.FetchEnvelope,
		//imap.FetchFlags,
		//imap.FetchInternalDate,
		section.FetchItem(),
	}

	log.Println(section.FetchItem())

	messages := make(chan *imap.Message)
	done := make(chan error, 1)
	go func() {
		done <- emailClient.Fetch(seqset, items, messages)
		log.Println("Fetch complete")
	}()

	for msg := range messages {
		if msg != nil {
			email, err := GetEmailFromImapMessage(msg, &section)
			if err != nil && err != io.EOF {
				log.Println("接收邮件时出现错误:" + err.Error())
			}

			fc(email)
		}
	}

	if len(seqset.Set) > 0 {
		err = emailClient.Store(seqset, imap.AddFlags, imap.SeenFlag, nil)
		if err != nil {
			return err
		}
	}

	if err := <-done; err != nil {
		return err
	}

	return
}

func MessageStore(imapSetting *ImapSetting, userinfo *UserInfo, seqNum uint32, storeItem imap.StoreItem, value string) (err error) {
	defer func() {
		if r_err, ok := recover().(error); ok && r_err != nil {
			log.Println("接受消息是发生错误:" + r_err.Error())
		}
	}()

	var emailClient *client.Client

	if imapSetting.UseSSL {
		emailClient, err = client.DialTLS(imapSetting.Url, nil)
	} else {
		emailClient, err = client.Dial(imapSetting.Url)
	}
	if err != nil {
		log.Fatal(err)
	}

	defer emailClient.Close()

	emailClient.Login(userinfo.UserName, userinfo.Password)
	defer emailClient.Logout()

	emailClient.Select("INBOX", true)

	seqset := new(imap.SeqSet)
	seqset.AddNum(seqNum)

	err = emailClient.Store(seqset, storeItem, value, nil)
	return
}

func GetEmailFromImapMessage(message *imap.Message, section *imap.BodySectionName) (email *Email, err error) {
	r := message.GetBody(section)
	if r == nil {
		err = errors.New("no message body")
		return
	}

	email = &Email{}

	email.SeqNum = message.SeqNum
	if message.Envelope != nil {
		email.MessageId = message.Envelope.MessageId
	}

	mr, err := mail.CreateReader(r)
	if err != nil {
		return
	}

	// Print some info about the message
	header := mr.Header
	date, err := header.Date()
	if err != nil {
		return
	}
	email.DateTime = date

	from_list, err := header.AddressList("From")
	if err != nil {
		return
	}
	if from_list != nil {
		var from_str []string
		for _, from := range from_list {
			from_str = append(from_str, from.Address)
		}
		email.From = from_str
	}

	to_list, err := header.AddressList("To")
	if err != nil {
		return
	}
	if to_list != nil {
		var to_str []string
		for _, to := range to_list {
			to_str = append(to_str, to.Address)
		}
		email.To = to_str
	}

	subject, err := header.Subject()
	if err != nil {
		return
	}
	email.Subject = subject

	for {
		var p *mail.Part
		p, err = mr.NextPart()
		if err == io.EOF {
			break
		} else if err != nil {
			return
		}

		switch h := p.Header.(type) {
		case *mail.InlineHeader:
			c_type := strings.ToLower(p.Header.Get("Content-Type"))
			var b []byte
			b, err = ioutil.ReadAll(p.Body)
			if err != nil {
				return
			}

			if strings.Contains(c_type, "text/plain") {
				email.TextBody = string(b)
			} else if strings.Contains(c_type, "text/html") {
				email.HtmlBody = string(b)
			}
		case *mail.AttachmentHeader:
			// This is an attachment
			filename, err := h.Filename()
			log.Println("Got attachment: %v", filename)

			if err != nil {
				log.Println(err.Error())
			}
		}
	}

	return
}
